「你介紹了兩種不同的模仿方法,所以是有什麼差別?」
「嗯… 你仔細想想,前天的我跟昨天的我有什麼差別?」艾草回。
「嗯….實在想不到有什麼差別耶?」
「你在回想看看外表,一定有差別的!」
「啊!我知道了!!!」
「猜到答案了嗎?」
「雖然你體重大概只有 8g 左右(綠繡眼型態),但我覺得昨天的你看起來比前天的你胖兩倍!!!」
「屁,那只是毛比較膨!」
今天來介紹一下 jest.fn()
、 spyOn
的差異!
首先來看一下範例程式碼,我們有兩個函式,其中 determineHypertension
函式幫我們確認是否有高血壓風險,而 informResult
函式幫忙印出是否有風險,當在執行 informResult
函式時是會透過呼叫 determineHypertension
函式來幫我們確認是否有高血壓。
// 確認是否有高血壓,回傳值為布林值的 true/false
function determineHypertension(systolic, diastolic) {
// systolic 收縮壓
// diastolic 舒張壓
return systolic >= 120 || diastolic >= 80;
}
function informResult(systolic, diastolic) {
return determineHypertension(systolic, diastolic)
? "請小心你有高血壓風險"
: "你沒有高血壓";
}
首先透過 jest.fn()
來模擬 determineHypertension
函式:
test('Introducing mocks', () => {
// 1.模擬
const mockFunction = jest.fn();
determineHypertension = mockFunction;
});
將函式 mock
起來後可以透過 Jest 提供的方法來設定回傳值為 false
,並宣告變數 result 且賦予 informResult(130, 90)
的回傳值:
test("Introducing mocks", () => {
// arrange
const mockFunction = jest.fn().mockReturnValue(false);
determineHypertension = mockFunction;
// act
const result = informResult(130, 90);
});
斷言結果
test("Introducing mocks", () => {
// arrange
const mockFunction = jest.fn().mockReturnValue(false);
determineHypertension = mockFunction;
// act
const result = informResult(130, 90);
// assert
expect(result).toBe("你沒有高血壓");
expect(mockFunction).toHaveBeenCalledTimes(1);
});
斷言結果這個步驟會發生一件事,透過 informResult
傳入的收縮壓與舒張壓皆超出高血壓標準,但結果還是印出你沒有高血壓。
這是因為當我們透過 jest.fn()
來模擬函式時它並不會去執行 determineHypertension
函式,而是直接拋出我們設定的回傳值 false
!
接下來讓我們用 spyOn
來嘗試,首先修改一下範例函式,基本邏輯的地方沒有變化,主要是將 determineHypertension
函式包入 object 內,並於呼叫時加上 object.
,方便 spyOn 測試使用:
const object = {
determineHypertension: (systolic, diastolic) => {
return systolic >= 120 || diastolic >= 80;
},
};
function informResult(systolic, diastolic) {
return object.determineHypertension(systolic, diastolic)
? "請小心你有高血壓風險"
: "你沒有高血壓";
}
首先先透過 jest.spyOn
來模擬 determineHypertension
函式:
test("Introducing spyOn", () => {
// arrange
const spyOnFunction = jest.spyOn(object, "determineHypertension");
});
接下來如同上方步驟二一樣宣告變數 result
並賦予值為 informResult(130, 90)
test("Introducing spyOn", () => {
// arrange
const spyOnFunction = jest.spyOn(object, "determineHypertension");
// act
const result = informResult(130, 90);
});
最後這個步驟如果跟上方 jest.fn() 用一樣的方法去斷言你沒有高血壓:
test("Introducing spyOn", () => {
// arrange
const spyOnFunction = jest.spyOn(object, "determineHypertension");
// act
const result = informResult(130, 90);
// assert
expect(result).toBe("你沒有高血壓");
expect(spyOnFunction).toHaveBeenCalledTimes(1);
});
會發現測試不通過:
jest.fn()
並不會實際去執行被 mock 的函式,而 spyOn 會去執行傳入的函式。
透過 jest.fn()
可以將 determineHypertension
與 informResult
的依賴完全切分開,單獨只測試 informResult
針對 true
/ false
的回傳值是否正確, spyOn
會去執行該函式。
不過 spyOn
也可以進一步使用 mock 方法如:mockImplementation
等去模擬回傳值或回傳函式,在這種情況下如果希望再次調用原始函式而非僅回傳模擬值,可以透過 mockRestore()
恢復成原始函式。
https://jestjs.io/docs/jest-object#jestspyonobject-methodname
https://medium.com/enjoy-life-enjoy-coding/jest-jojo是你-我的替身能力是-mock-4de73596ea6e
https://www.youtube.com/watch?v=1Xafx6o82Aw&ab_channel=SoftwareTestingHelp
https://dwatow.github.io/2020/04-24-jest/jest-doc-5/
jest.fn() 並不會實際去執行被 mock 的函式,而 spyOn 會去執行傳入的函式
請問有建議用哪一種嗎?
個人疑問~